'''
Created on 19/05/2010

@author: eh01
'''
from datastore.resource import Resource
from optparse import OptionParser
from datastore.uri import Uri
import os
import urllib
import csv
import re
import sys

DATA_GROUPING = 'data_grouping'
SPEAR_SCREEN_ELEMENT_NAME = 'spear_screen_element_name'
SPEAR_ALIAS = 'spear_alias'
SPEAR_ELEMENT_TYPE = 'spear_element_type'
SPEAR_ELEMENT_MIN_OCCURS = 'spear_element_min_occurs'
SPEAR_ELEMENT_MAX_OCCURS = 'spear_element_max_occurs'
SPEAR_ELEMENT_DESCRIPTION = 'spear_element_description'
SPEAR_ELEMENT_NOTES = 'spear_element_notes'
SPEAR_ELEMENT_DOMAIN = 'spear_element_domain'
SPEAR_ADAPTOR_MAPPING_NOTES = 'spear_adaptor_mapping_notes'
DATA_URI = 'data_uri'
CONSTANT = 'constant'
CHECK_DATA_URI = 'check_data_uri'
CONDITION = 'condition'
EDAIS_TRANSFORMATION_INSTRUCTIONS = 'edais_transformation_instructions'
EDAIS_NATIONAL_PATH = 'edais_national_path'
EDAIS_ELEMENT_DESCRIPTION = 'edais_element_description'
EDAIS_ELEMENT_NOTES = 'edais_element_notes'
EDAIS_ELEMENT_DOMAIN = 'edais_element_domain'
QUERYREQUESTPUBLICAPPLICATIONSTATUSQUERY = 'queryrequestpublicapplicationstatusquery'
REPLYPUBLICSTATUSAPPLICATIONRESPONSE = 'replypublicstatusapplicationresponse'
DECLARESSAVESTATUSCHANGEAPPLICATIONNOTIFICATION = 'declaressavestatuschangeapplicationnotification'
RECEIPTACKNOWLEDGEMENTSIGNAL = 'receiptacknowledgementsignal'
RECEIPTEXCEPTIONSIGNAL = 'receiptexceptionsignal'

VARIABLES = {
             0:DATA_GROUPING,
             1:SPEAR_SCREEN_ELEMENT_NAME,
             2:SPEAR_ALIAS,
             3:SPEAR_ELEMENT_TYPE,
             4:SPEAR_ELEMENT_MIN_OCCURS,
             5:SPEAR_ELEMENT_MAX_OCCURS,
             6:SPEAR_ELEMENT_DESCRIPTION,
             7:SPEAR_ELEMENT_NOTES,
             8:SPEAR_ELEMENT_DOMAIN,
             9:SPEAR_ADAPTOR_MAPPING_NOTES,
             10:DATA_URI,
             11:CONSTANT,
             12:CHECK_DATA_URI,
             13:CONDITION,
             14:EDAIS_TRANSFORMATION_INSTRUCTIONS,
             15:EDAIS_NATIONAL_PATH,
             16:EDAIS_ELEMENT_DESCRIPTION,
             17:EDAIS_ELEMENT_NOTES,
             18:EDAIS_ELEMENT_DOMAIN,
             19:QUERYREQUESTPUBLICAPPLICATIONSTATUSQUERY,
             20:REPLYPUBLICSTATUSAPPLICATIONRESPONSE,
             21:DECLARESSAVESTATUSCHANGEAPPLICATIONNOTIFICATION,
             22:RECEIPTACKNOWLEDGEMENTSIGNAL,
             23:RECEIPTEXCEPTIONSIGNAL   
             }

COLUMNS = {
           0:'Data Grouping',
           1:'SPEAR Screen Element Name',
           2:'SPEAR Alias',
           3:'SPEAR Element Type',
           4:'SPEAR Element Min Occurs',
           5:'SPEAR Element Max Occurs',
           6:'SPEAR Element Description',
           7:'SPEAR Element Notes',
           8:'SPEAR Element Domain',
           9:'SPEAR Adaptor Mapping Notes',
           10:'Data URI',
           11:'Constant',
           12:'Check Data URI',
           13:'Condition',
           14:'eDAIS Transformation Instructions',
           15:'eDAIS National Path',
           16:'eDAIS Element Description',
           17:'eDAIS Element Notes',
           18:'eDAIS Element Domain',
           19:'QueryRequestPublicApplicationStatusQuery',
           20:'ReplyPublicStatusApplicationResponse',
           21:'DeclaresSaveStatusChangeApplicationNotification',
           22:'ReceiptAcknowledgementSignal',
           23:'ReceiptExceptionSignal'
           }

cp1252_to_unicode = {
    # from http://www.microsoft.com/typography/unicode/1252.htm
    u"\x80": u"\u20AC", # EURO SIGN
    u"\x82": u"\u201A", # SINGLE LOW-9 QUOTATION MARK
    u"\x83": u"\u0192", # LATIN SMALL LETTER F WITH HOOK
    u"\x84": u"\u201E", # DOUBLE LOW-9 QUOTATION MARK
    u"\x85": u"\u2026", # HORIZONTAL ELLIPSIS
    u"\x86": u"\u2020", # DAGGER
    u"\x87": u"\u2021", # DOUBLE DAGGER
    u"\x88": u"\u02C6", # MODIFIER LETTER CIRCUMFLEX ACCENT
    u"\x89": u"\u2030", # PER MILLE SIGN
    u"\x8A": u"\u0160", # LATIN CAPITAL LETTER S WITH CARON
    u"\x8B": u"\u2039", # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
    u"\x8C": u"\u0152", # LATIN CAPITAL LIGATURE OE
    u"\x8E": u"\u017D", # LATIN CAPITAL LETTER Z WITH CARON
    u"\x91": u"\u2018", # LEFT SINGLE QUOTATION MARK
    u"\x92": u"\u2019", # RIGHT SINGLE QUOTATION MARK
    u"\x93": u"\u201C", # LEFT DOUBLE QUOTATION MARK
    u"\x94": u"\u201D", # RIGHT DOUBLE QUOTATION MARK    
    u"\x95": u"\u2022", # BULLET
    u"\x96": u"\u2013", # EN DASH
    u"\x97": u"\u2014", # EM DASH
    u"\x98": u"\u02DC", # SMALL TILDE
    u"\x99": u"\u2122", # TRADE MARK SIGN
    u"\x9A": u"\u0161", # LATIN SMALL LETTER S WITH CARON
    u"\x9B": u"\u203A", # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
    u"\x9C": u"\u0153", # LATIN SMALL LIGATURE OE
    u"\x9E": u"\u017E", # LATIN SMALL LETTER Z WITH CARON
    u"\x9F": u"\u0178", # LATIN CAPITAL LETTER Y WITH DIAERESIS
}

cp1252_to_ascii = {
    u"\x91": "'", # LEFT SINGLE QUOTATION MARK
    u"\x92": "'", # RIGHT SINGLE QUOTATION MARK
    u"\x93": '"', # LEFT DOUBLE QUOTATION MARK
    u"\x94": '"', # RIGHT DOUBLE QUOTATION MARK
    u"\x96": "-", # EN DASH
                   }

def remove_cp1252_characters(text):
    """
    This function replaces non ASCII character (by assuming they are CP1252 characters)
    with their ascii "equivalent". This code has been copied
    and modified from http://effbot.org/zone/unicode-gremlins.htm

    :param text: string 
    :rtype: None    
    """
    # map cp1252 to their 8bit equivalent
    if re.search(u"[\x80-\x9f]", text):
        def fixup(m):
            s = m.group(0)
            b = cp1252_to_ascii.get(s, s)
            return b
        if isinstance(text, type("")):
            # make sure we have a unicode string
            text = unicode(text, "iso-8859-1")
        text = re.sub(u"[\x80-\x9f]", fixup, text)
    return text
    
def check_headers(row):
    """
    This function raises an Exception if csv does not have the headers in an order expected.
    
    :param row: list 
    :rtype: None
    """
    if len(row) != len(COLUMNS):
        raise Exception("Number of headers is unexpected. Expected %d, found %d" % (len(COLUMNS), len(row)))
    for i in range(len(row)):
        if row[i] != COLUMNS[i]:
            raise Exception("Ordering of headers unexpected. Expected %s, found %s" % (COLUMNS[i], row[i]))
    return

def generate_resource_from_row(row):
    """
    This generates a resource from a row in the mapping spreadsheet. Returns None, if
    all the "cells" are empty for the row.
    
    :param row: list 
    :rtype: Resource
    """
    # when the mapping excel spreadsheet is exported as csv, there are a number of rows at the end
    # of the csv file that are just blank, we want to ignore these rows that are empty.
    row_has_content = False
    res = Resource(type='term:eplanning/mapping/row')
    for i in range(len(row)):
        cell_value = remove_cp1252_characters(row[i])
        if not row_has_content and cell_value:
            row_has_content = True
        res.set_var(VARIABLES[i], remove_cp1252_characters(row[i]))
    
    i += 1
    # set the rest of the variables to None
    while i < len(VARIABLES):
        res.set_var(VARIABLES[i], "")
        i += 1
    if row_has_content:
        if res.get_var(EDAIS_NATIONAL_PATH):
            res.set_metadata(Uri("term:base/build/autoload_suffix"), Uri(urllib.quote(res.get_var(EDAIS_NATIONAL_PATH))))
        else:
            raise Exception('Cannot generate autoload_suffix')
        return res
    else:
        return None

def update_mapping_index(mapping_index, mapping_res, mapping_config_rs):
    """
    Each mapping resource will be located in spaces, and the uri of each mapping resource will be need to known by
    any function that requires the mapping resource. This function takes a mapping resource and 
    adds a mapping between the mapping resource and the messages it is associated with. 
    This mapping is stored in the dictionary (mapping_index) passed in.
    
    :param mapping_index: dictionary that maps eDAIS paths to the uri of a mapping resource.
    :param mapping_res: mapping resource that will be in spaces.
    :param mapping_config_res: configuration resource for the mapping package
    :rtype: None
    """
    mapping_res_uri =  mapping_config_rs.definitions['term:eplanning/mapping/row'].autoload_prefix + \
                        mapping_res.get_metadata(Uri("term:base/build/autoload_suffix"))
    if mapping_res.get_var(QUERYREQUESTPUBLICAPPLICATIONSTATUSQUERY) == 'Yes':
        mapping_index[QUERYREQUESTPUBLICAPPLICATIONSTATUSQUERY].append(mapping_res_uri)
    if mapping_res.get_var(REPLYPUBLICSTATUSAPPLICATIONRESPONSE) == 'Yes':
        mapping_index[REPLYPUBLICSTATUSAPPLICATIONRESPONSE].append(mapping_res_uri)
    if mapping_res.get_var(DECLARESSAVESTATUSCHANGEAPPLICATIONNOTIFICATION) == 'Yes':
        mapping_index[DECLARESSAVESTATUSCHANGEAPPLICATIONNOTIFICATION].append(mapping_res_uri)
    if mapping_res.get_var(RECEIPTACKNOWLEDGEMENTSIGNAL) == 'Yes':
        mapping_index[RECEIPTACKNOWLEDGEMENTSIGNAL].append(mapping_res_uri)
    if mapping_res.get_var(RECEIPTEXCEPTIONSIGNAL) == 'Yes':
        mapping_index[RECEIPTEXCEPTIONSIGNAL].append(mapping_res_uri)
        
def remove_existing_mapping_related_resources():
    """
    Deletes all existing mapping related resources from the artefacts folder.
    
    :rtype: None
    """       
    files = os.listdir('artefacts')
    for file in files:
        if file.endswith('.xml'):
            os.remove(os.path.join('artefacts', file))

def create_mapping_index(mapping_index):
    """
    Creates Mapping Index Resource.
    
    :param mapping_index: dictionary of mapping index
    :rtype: Resource
    """        
    rs = Resource(type="term:eplanning/mapping/index")
    rs.mapping_index = mapping_index
    rs.set_metadata(Uri("term:base/build/autoload_suffix"), Uri("index"))
    return rs

def write_out_mapping_resources(mapping_resources, mapping_index_rs):
    """
    Writes out the serialised version of the mapping resources and mapping index to the artefacts folder.
    
    :param mapping_resources: List of mapping resources.
    :rtype: None
    """
    for mapping_resource in mapping_resources:
        new_artefact_file = open("artefacts/row_%s.xml" % mapping_resource.get_var(EDAIS_NATIONAL_PATH).replace('/', '-'), "w")
        new_artefact_file.write(mapping_resource.serialise())
        new_artefact_file.close()    
    
    new_artefact_file = open("artefacts/mapping_index.xml", "w")
    new_artefact_file.write(mapping_index_rs.serialise())
    new_artefact_file.close()
        
def generate_mapping_artefacts_from_spreadsheet(mapping_file_path):
    """
    Generates mapping artefacts from spreadsheet and saves them in the artefacts directory.
    
    :param mapping_file_path: Absolute path of the mapping spreadsheet file.
    :rtype: None
    """        
    mapping_resources = []
    mapping_index = {}
    mapping_index[QUERYREQUESTPUBLICAPPLICATIONSTATUSQUERY] = []
    mapping_index[REPLYPUBLICSTATUSAPPLICATIONRESPONSE] = []
    mapping_index[DECLARESSAVESTATUSCHANGEAPPLICATIONNOTIFICATION] = []
    mapping_index[RECEIPTACKNOWLEDGEMENTSIGNAL] = []
    mapping_index[RECEIPTEXCEPTIONSIGNAL] = []

    remove_existing_mapping_related_resources()
    csv_file = open(mapping_file_path, 'r')    
    csv_reader = csv.reader(csv_file, dialect=csv.excel)
    check_headers(csv_reader.next())
    mapping_config_rs = Resource(type="temp")
    mapping_config_rs.load_from_file('configuration.xml')  
    for row in csv_reader:
        mapping_res = generate_resource_from_row(row)
        if mapping_res:
            update_mapping_index(mapping_index, mapping_res, mapping_config_rs)
            mapping_resources.append(mapping_res)
               
    mapping_index_rs = create_mapping_index(mapping_index)
    write_out_mapping_resources(mapping_resources, mapping_index_rs)

    print "%d mapping resources added" % len(mapping_resources)
        
if __name__ == '__main__':
    usage = "%s options. "
    parser = OptionParser(usage=usage % sys.argv[0])

    parser.add_option("--mapping-spreadsheet", 
                      dest="mapping_spreadsheet_url", 
                      help=u"Mapping spreadsheet URL.")   

    (options, args) = parser.parse_args()    
    generate_mapping_artefacts_from_spreadsheet(options.mapping_spreadsheet_url)